package storage

import (
	"bytes"
	bolt "gitee.com/wanttobeamaster/bbolt"
	"gitee.com/wanttobeamaster/gridbase/pkg/pb/raftcmdpb"
	"gitee.com/wanttobeamaster/gridbase/pkg/util"
	"github.com/fagongzi/log"
	"math"
)

var (
	ScoreMin int64 = math.MinInt64 + 2
	ScoreMax int64 = math.MaxInt64 - 1
)

type ZSetObj struct {
	Size uint64
}

func MarshalZSetObj(obj *ZSetObj) []byte {
	totalLen := 8
	raw := make([]byte, totalLen)

	idx := 0
	util.Uint64ToBytes1(raw[idx:], obj.Size)

	return raw
}

func UnmarshalZSetObj(raw []byte) (*ZSetObj, error) {
	if len(raw) != 8 {
		return nil, nil
	}
	obj := ZSetObj{}
	idx := 0
	obj.Size, _ = util.BytesToUint64(raw[idx:])
	return &obj, nil
}

type boltZSetEngine struct {
	db      *bolt.DB
	limiter util.Limiter
}

func newBoltZSetEngine(db *bolt.DB) ZSetEngine {
	db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte("boltZSetBucket"))
		if err != nil {
			return err
		}
		return nil
	})
	return &boltZSetEngine{
		db:      db,
		limiter: *util.NewLimiter(LimitConcurrencyWrite),
	}
}

func (e *boltZSetEngine) ZSetMetaObj(key []byte) (*ZSetObj, error) {
	var (
		v   []byte
		err error
	)

	metaKey := RawKeyPrefix(key)

	err = e.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))
		v = b.Get(metaKey)
		return nil
	})

	if err != nil {
		return nil, err
	}
	if v == nil {
		return nil, nil
	}
	obj, err := UnmarshalZSetObj(v)
	if err != nil {
		return nil, nil
	}
	return obj, nil
}

func (e *boltZSetEngine) newZSetMetaObj() *ZSetObj {
	return &ZSetObj{
		Size: 0,
	}
}

func (e *boltZSetEngine) RawZSetDataKey(key, member []byte) []byte {
	keyPrefix := RawKeyPrefix(key)
	dataKey := append(keyPrefix, util.DataTypeKey)
	dataKey = append(dataKey, member...)
	return dataKey
}

func (e *boltZSetEngine) RawZSetScoreKey(key, member []byte, score int64) []byte {
	keyPrefix := RawKeyPrefix(key)
	scoreKey := append(keyPrefix, util.ScoreTypeKey)
	scoreBytes, _ := util.Uint64ToBytes(util.ZScoreOffset(score))
	scoreKey = append(scoreKey, scoreBytes...)
	scoreKey = append(scoreKey, member...)
	return scoreKey
}

type MemberPair struct {
	Score  int64
	Member []byte
}

/*
	implements relative functions
*/
func (e *boltZSetEngine) ZAdd(key []byte, score float64, member []byte) (int64, error) {
	if len(key) == 0 {
		return 0, ErrEmptyKey
	}

	// xiaoxiao
	eMetaKey := RawKeyPrefix(key)

	metaObj, err := e.ZSetMetaObj(key)

	if err != nil {
		return 0, err
	}
	if metaObj == nil {
		metaObj = e.newZSetMetaObj()
	}

	var added int64

	//add data key and score key for each member pair
	UpdateErr := e.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))
		//pre-parse ZSet data structure
		eDatakey := e.RawZSetDataKey(key, member)
		eScoreKey := e.RawZSetScoreKey(key, member, int64(score))
		score, err := util.Int64ToBytes(int64(score))
		if err != nil {
			return nil
		}

		v := b.Get(eDatakey)
		if v == nil {
			//member not exists
			metaObj.Size++
			added++
		} else {
			//member exists
			oldScore, err := util.BytesToInt64(v)
			if err != nil {
				return err
			}
			//delete old ScoreKey
			oldScoreKey := e.RawZSetScoreKey(key, member, oldScore)
			err = b.Delete(oldScoreKey)
			if err != nil {
				return err
			}
		}

		// xiaoxiao :
		marshalMetakey := MarshalZSetObj(metaObj)
		err = b.Put(eMetaKey , marshalMetakey)
		if err != nil {
			return err
		}

		err = b.Put(eDatakey, score)
		if err != nil {
			return err
		}

		err = b.Put(eScoreKey, []byte{0})
		if err != nil {
			return err
		}
		return nil
	})
	if UpdateErr != nil {
		return 0, UpdateErr
	}
	return added, nil
}

func (e *boltZSetEngine) ZCard(key []byte) (int64, error) {
	if len(key) == 0 {
		return 0, ErrEmptyKey
	}

	metaObj, err := e.ZSetMetaObj(key)
	if err != nil {
		return 0, err
	}
	if metaObj == nil {
		return 0, nil
	}
	return int64(metaObj.Size), nil
}

func (e *boltZSetEngine) ZCount(key []byte, min []byte, max []byte) (int64, error) {
	if len(key) == 0 {
		return 0, ErrEmptyKey
	}

	var count int64

	lowerBound, _ := util.BytesToInt64(min)
	upperBound, _ := util.BytesToInt64(max)
	if lowerBound > upperBound {
		return 0, nil
	}

	metaObj, err := e.ZSetMetaObj(key)
	if err != nil {
		return 0, err
	}
	if metaObj == nil {
		return 0, nil
	}

	// []byte{0} is error Key
	startKey := e.RawZSetScoreKey(key, []byte{0}, lowerBound)
	endKey := e.RawZSetScoreKey(key, []byte{0}, upperBound)



	ViewErr := e.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))
		c := b.Cursor()
		for k, _ := c.Seek(startKey); k != nil && bytes.Compare(k, endKey) <= 0; c.Next() {
			count++
		}
		return nil
	})
	return count, ViewErr
}

func (e *boltZSetEngine) ZIncrBy(key []byte, member []byte, by float64) ([]byte, error) {
	if len(key) == 0 || len(member) == 0 {
		return nil, ErrEmptyKey
	}

	eMetaKey := RawKeyPrefix(key)

	metaObj, err := e.ZSetMetaObj(key)
	if err != nil {
		return nil, err
	}
	if metaObj == nil {
		return nil, nil
	}

	var (
		newScore  int64
		eScoreKey []byte
		delta     int64
	)

	delta = int64(by)

	UpdateErr := e.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))

		eDataKey := e.RawZSetDataKey(key, member)
		s := b.Get(eDataKey)
		if s == nil {
			// member not exists, add it with new score
			metaObj.Size++
			newScore = delta
			eScoreKey = e.RawZSetScoreKey(key, member, newScore)

			// add data key and score key, then update meta key
			scoreRaw, _ := util.Int64ToBytes(newScore)
			err := b.Put(eDataKey, scoreRaw)
			if err != nil {
				return err
			}

			err = b.Put(eScoreKey, []byte{0})
			if err != nil {
				return err
			}

			eMetaValue := MarshalZSetObj(metaObj)
			err = b.Put(eMetaKey, eMetaValue)
			if err != nil {
				return err
			}
		} else {
			//get the member score
			scoreRaw := b.Get(eDataKey)
			if scoreRaw == nil {
				return ErrInvalidMeta
			}

			score, _ := util.BytesToInt64(scoreRaw)

			newScore = score + delta

			// update datakey
			scoreRaw, _ = util.Int64ToBytes(newScore)
			err := b.Put(eDataKey, scoreRaw)
			if err != nil {
				return err
			}

			eScoreKey = e.RawZSetScoreKey(key, member, score)
			err = b.Delete(eScoreKey)
			if err != nil {
				return err
			}

			eScoreKey = e.RawZSetScoreKey(key, member, newScore)
			err = b.Put(eScoreKey, []byte{0})
			if err != nil {
				return err
			}
		}
		return nil
	})

	if UpdateErr != nil {
		return nil, UpdateErr
	}

	newScoreBytes, _ := util.Int64ToBytes(newScore)
	return newScoreBytes, nil
}

func (e *boltZSetEngine) ZLexCount(key []byte, min []byte, max []byte) (int64, error) {
	if len(key) == 0 || len(min) == 0 || len(max) == 0 {
		return 0, ErrEmptyKey
	}

	// start and stop must prefix with -/+/(/[
	if !checkPrefixValid(min) || !checkPrefixValid(max) {
		return 0, ErrEmptyKey
	}

	var (
		err   error
		count int64
	)

	metaObj, err := e.ZSetMetaObj(key)
	if err != nil {
		return 0, err
	}
	if metaObj == nil {
		return 0, nil
	}

	var (
		eStartKey, eEndKey []byte
		withStart, withEnd bool
	)

	eStartKey, withStart = e.zlexParse(key, min)
	eEndKey, withEnd = e.zlexParse(key, max)

	ViewErr := e.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))
		c := b.Cursor()
		for k, _ := c.Seek(eStartKey); k != nil && bytes.Compare(k, eEndKey) <= 0; c.Next() {
			count++
		}
		return nil
	})

	if !withStart {
		count--
	}
	if !withEnd {
		count--
	}

	return count, ViewErr
}

func (e *boltZSetEngine) ZRange(key []byte, start int64, stop int64) ([]*raftcmdpb.ScorePair, error) {
	if len(key) == 0 {
		return nil, ErrEmptyKey
	}

	var (
		err     error
		members [][]byte
	)

	if start > stop && (start < 0 || stop > 0) {
		return nil, nil
	}

	startKey := e.RawZSetScoreKey(key, []byte{0}, ScoreMin)
	endKey := e.RawZSetScoreKey(key, []byte{0}, ScoreMax)

	offset, count, err := e.zRangeParse(key, start, stop, false)
	if err != nil {
		return nil, err
	}
	// key not exist or marked deleted
	if offset == 0 && count == 0 {
		return nil, nil
	}

	var resp []*raftcmdpb.ScorePair
	// get all key range slice
	ViewErr := e.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))
		c := b.Cursor()
		//refer to Tidis/tikv.go : func getRangeKeysWithFrontier
		for k, _ := c.Seek(startKey); c != nil && k != nil && count > 0 && bytes.Compare(k, endKey) <= 0; k, _ = c.Next() {
			if offset > 0 {
				offset--
				continue
			}
			members = append(members, k)
			count--
		}

		return nil
	})
	if ViewErr != nil {
		return nil, ViewErr
	}

	//copy [score, member] into resp
	respLen := len(members)
	resp = make([]*raftcmdpb.ScorePair, respLen)
	keyPrefixLen := len(RawKeyPrefix(key))

	for i, m := range members {
		score, member, _ := util.ZScoreDecoder(keyPrefixLen, m)
		resp[i] = &raftcmdpb.ScorePair{
			Score:  float64(score),
			Member: member,
		}
	}
	return resp, nil
}

func (e *boltZSetEngine) ZRangeByLex(key []byte, min []byte, max []byte) ([][]byte, error) {
	if len(key) == 0 || len(min) == 0 || len(max) == 0 {
		return nil, ErrEmptyKey
	}

	if !checkPrefixValid(min) || !checkPrefixValid(max) {
		return nil, ErrEmptyKey
	}

	var (
		err                error
		eStartKey, eEndKey []byte
		withStart, withEnd bool
		members            [][]byte
	)

	metaObj, err := e.ZSetMetaObj(key)
	if err != nil {
		return nil, err
	}
	if metaObj == nil {
		return nil, nil
	}

	eStartKey, withStart = e.zlexParse(key, min)
	eEndKey, withEnd = e.zlexParse(key, max)

	ViewErr := e.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))
		c := b.Cursor()
		//refer to Tidis/tikv.go : func getRangeKeysWithFrontier
		for k, _ := c.Seek(eStartKey); c != nil && k != nil && bytes.Compare(k, eEndKey) <= 0; k, _ = c.Next() {
			if !withStart && bytes.Compare(k, eStartKey) == 0 {
				continue
			}
			if !withEnd && bytes.Compare(k, eEndKey) == 0 {
				continue
			}
			members = append(members, k)
		}
		return nil
	})
	if ViewErr != nil {
		return nil, ViewErr
	}
	return members, nil
}

func (e *boltZSetEngine) ZRangeByScore(key []byte, min []byte, max []byte) ([]*raftcmdpb.ScorePair, error) {
	if len(key) == 0 {
		return nil, ErrEmptyKey
	}

	var members [][]byte

	metaObj, err := e.ZSetMetaObj(key)
	if err != nil {
		return nil, err
	}
	if metaObj == nil {
		return nil, nil
	}

	startNum, err := util.BytesToInt64(min)
	endNum, err := util.BytesToInt64(max)
	startKey := e.RawZSetScoreKey(key, []byte{0}, startNum)
	endKey := e.RawZSetScoreKey(key, []byte{0}, endNum+1)

	ViewErr := e.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))
		c := b.Cursor()
		zz := metaObj.Size
		for k, _ := c.Seek(startKey); k != nil && bytes.Compare(k, endKey) <= 0 && zz > 0; c.Next() {
			members = append(members, k)
			zz--
		}
		return nil
	})
	if ViewErr != nil {
		return nil, ViewErr
	}

	respLen := len(members)
	resp := make([]*raftcmdpb.ScorePair, respLen)
	keyPrefixLen := len(RawKeyPrefix(key))

	for i, m := range members {
		score, member, _ := util.ZScoreDecoder(keyPrefixLen, m)
		resp[i] = &raftcmdpb.ScorePair{
			Score:  float64(score),
			Member: member,
		}
	}
	return resp, nil
}

func (e *boltZSetEngine) ZRank(key []byte, member []byte) (int64, error) {
	if len(key) == 0 {
		return -1, ErrEmptyKey
	}

	startKey := e.RawZSetScoreKey(key, []byte{0}, ScoreMin)
	endKey := e.RawZSetScoreKey(key, []byte{0}, ScoreMax)

	objScoreRaw, _ := e.ZScore(key, member)
	if objScoreRaw == nil {
		return -1, nil
	}
	objScore, _ := util.BytesToInt64(objScoreRaw)
	objKey := e.RawZSetScoreKey(key, member, objScore)

	var rank int64

	ViewErr := e.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))
		c := b.Cursor()
		for k, _ := c.Seek(startKey); k != nil && bytes.Compare(k, endKey) < 0 && bytes.Compare(k, objKey) < 0; c.Next() {
			rank++
		}
		return nil
	})
	if ViewErr != nil {
		return -1, ViewErr
	}
	return rank, nil
}

func (e *boltZSetEngine) ZRem(key []byte, members ...[]byte) (int64, error) {
	if len(key) == 0 || len(members) == 0 {
		return 0, ErrEmptyKey
	}

	metaObj, err := e.ZSetMetaObj(key)
	if err != nil {
		return 0, err
	}
	if metaObj == nil {
		return 0, nil
	}

	var deleted int64

	BatchErr := e.db.Batch(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))

		for _, member := range members {
			eDataKey := e.RawZSetDataKey(key, member)

			scoreRaw := b.Get(eDataKey)
			if scoreRaw == nil {
				// member not exists
				continue
			}

			deleted++

			score, err := util.BytesToInt64(scoreRaw)
			if err != nil {
				return err
			}

			eScoreKey := e.RawZSetScoreKey(key, member, score)

			err = b.Delete(eDataKey)
			if err != nil {
				return err
			}
			err = b.Delete(eScoreKey)
			if err != nil {
				return err
			}
		}

		if int64(metaObj.Size) < deleted {
			return ErrInvalidMeta
		}

		//update meta
		metaObj.Size -= uint64(deleted)

		eMetaKey := RawKeyPrefix(key)
		eMetaValue := MarshalZSetObj(metaObj)
		err = b.Put(eMetaKey, eMetaValue)
		if err != nil {
			return err
		}
		return nil
	})

	if BatchErr != nil {
		return 0, BatchErr
	}

	return deleted, nil
}

func (e *boltZSetEngine) ZRemRangeByLex(key []byte, min []byte, max []byte) (int64, error) {
	if len(key) == 0 || len(min) == 0 || len(max) == 0 {
		return 0, ErrEmptyKey
	}

	metaObj, err := e.ZSetMetaObj(key)
	if err != nil {
		return 0, err
	}
	if metaObj == nil {
		return 0, nil
	}

	var (
		deleted            uint64
		eStartKey, eEndKey []byte
		withStart, withEnd bool
		members            [][]byte
	)

	UpdateErr := e.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))
		c := b.Cursor()
		zz := metaObj.Size
		for k, _ := c.Seek(eStartKey); c != nil && k != nil && bytes.Compare(k, eEndKey) <= 0 && zz > 0; k, _ = c.Next() {
			if !withStart && bytes.Compare(k, eStartKey) == 0 {
				continue
			}
			if !withEnd && bytes.Compare(k, eEndKey) == 0 {
				continue
			}
			zz--
			members = append(members, k)
		}

		deleted = uint64(len(members))

		if metaObj.Size < deleted {
			return ErrInvalidMeta
		}

		eMetaKey := RawKeyPrefix(key)
		keyPrefixLen := len(eMetaKey)

		// delete all members in score and data
		for _, member := range members {
			mem := member[keyPrefixLen+1:]
			// generate score key
			scoreRaw := b.Get(member)

			score, _ := util.BytesToInt64(scoreRaw)
			eScoreKey := e.RawZSetScoreKey(key, mem, score)

			err = b.Delete(member)
			if err != nil {
				return err
			}

			err = b.Delete(eScoreKey)
			if err != nil {
				return err
			}
		}

		metaObj.Size = metaObj.Size - deleted
		// update meta
		if metaObj.Size == 0 {
			// delete meta key
			err = b.Delete(eMetaKey)
			if err != nil {
				return err
			}
		} else {
			// update meta key
			eMetaValue := MarshalZSetObj(metaObj)
			err = b.Put(eMetaKey, eMetaValue)
			if err != nil {
				return err
			}
		}
		return nil
	})
	if UpdateErr != nil {
		return 0, UpdateErr
	}

	return int64(deleted), nil
}

func (e *boltZSetEngine) ZRemRangeByRank(key []byte, start int64, stop int64) (int64, error) {
	if len(key) == 0 {
		return -1, ErrEmptyKey
	}

	metaObj, err := e.ZSetMetaObj(key)
	if err != nil {
		return 0, err
	}
	if metaObj == nil {
		return 0, nil
	}

	if start < 0 {
		start = 0
	}

	if stop > start {
		return -1, ErrEmptyKey
	}
	startKey := e.RawZSetScoreKey(key, []byte{0}, ScoreMin)
	endKey := e.RawZSetScoreKey(key, []byte{0}, ScoreMax)

	var (
		members [][]byte
		rank    int64
		deleted uint64
	)

	BatchErr := e.db.Batch(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))
		c := b.Cursor()
		for k, _ := c.Seek(startKey); k != nil && bytes.Compare(k, endKey) < 0; c.Next() {
			if rank < start {
				continue
			} else if rank > stop {
				break
			}
			members = append(members, k)
			rank++
		}

		deleted = uint64(len(members))

		if metaObj.Size < deleted {
			return ErrInvalidMeta
		}

		eMetaKey := RawKeyPrefix(key)
		keyPrefixLen := len(eMetaKey)

		// delete all members in score and data
		for _, member := range members {
			mem := member[keyPrefixLen+1:]
			// generate score key
			scoreRaw := b.Get(member)

			score, _ := util.BytesToInt64(scoreRaw)
			eScoreKey := e.RawZSetScoreKey(key, mem, score)

			err = b.Delete(member)
			if err != nil {
				return err
			}
			err = b.Delete(eScoreKey)
			if err != nil {
				return err
			}
		}
		return nil
	})

	if BatchErr != nil {
		return 0, BatchErr
	}

	return int64(deleted), nil
}

func (e *boltZSetEngine) ZRemRangeByScore(key []byte, min []byte, max []byte) (int64, error) {

	startNum, _ := util.BytesToInt64(min)
	endNum, _ := util.BytesToInt64(max)
	startKey := e.RawZSetScoreKey(key, []byte{0}, startNum)
	endKey := e.RawZSetScoreKey(key, []byte{0}, endNum+1)

	var (
		members [][]byte
		deleted int64
	)

	metaObj, err := e.ZSetMetaObj(key)
	if err != nil {
		return 0, err
	}
	if metaObj == nil {
		return 0, nil
	}

	BatchErr := e.db.Batch(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))
		c := b.Cursor()
		zz := metaObj.Size
		for k, _ := c.Seek(startKey); k != nil && bytes.Compare(k, endKey) <= 0 && zz > 0; c.Next() {
			members = append(members, k)
			zz--
		}

		log.Debugf("zset clear members:%d", len(members))
		prefixKeyLen := len(RawKeyPrefix(key))

		//delete each score key and data key
		for _, member := range members {
			_, mem, err := util.ZScoreDecoder(prefixKeyLen, member)
			if err != nil {
				return err
			}

			//encode data key
			eDataKey := e.RawZSetDataKey(key, mem)

			err = b.Delete(member)
			if err != nil {
				return err
			}

			err = b.Delete(eDataKey)
			if err != nil {
				return err
			}
		}

		deleted = int64(len(members))

		//update zsize
		metaObj.Size -= uint64(deleted)
		eMetaKey := RawKeyPrefix(key)
		if metaObj.Size != 0 {
			eMetaValue := MarshalZSetObj(metaObj)
			err = b.Put(eMetaKey, eMetaValue)
			if err != nil {
				return err
			}
		} else {
			//delete meta key
			err = b.Delete(eMetaKey)
			if err != nil {
				return err
			}
		}
		return nil
	})

	if BatchErr != nil {
		return 0, BatchErr
	}

	return deleted, nil
}

func (e *boltZSetEngine) ZScore(key []byte, member []byte) ([]byte, error) {
	if len(key) == 0 || len(member) == 0 {
		return nil, ErrEmptyKey
	}

	var scoreRaw []byte

	metaObj, err := e.ZSetMetaObj(key)
	if err != nil {
		return nil, err
	}
	if metaObj == nil {
		return []byte{'0'}, nil
	}

	//getScoreRaw
	eDataKey := e.RawZSetDataKey(key, member)
	e.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltZSetBucket"))
		scoreRaw = b.Get(eDataKey)
		return nil
	})

	return scoreRaw, nil
}

/*
	ZSet utils
*/
func checkPrefixValid(a []byte) bool {
	if len(a) == 0 {
		return false
	}
	switch a[0] {
	case '-':
		return true
	case '+':
		return true
	case '(':
		return true
	case '[':
		return true
	default:
		return false
	}
}

func (e *boltZSetEngine) zlexParse(key, lex []byte) ([]byte, bool) {
	if len(lex) == 0 {
		return nil, false
	}
	var lexKey []byte
	var withFrontier bool
	var m []byte

	switch lex[0] {
	case '-':
		m = []byte{0}
	case '+':
		m = append(lex[1:], byte(0))
	case '(':
		m = lex[1:]
		withFrontier = false
	case '[':
		m = lex[1:]
		withFrontier = true
	default:
		return nil, false
	}
	lexKey = e.RawZSetDataKey(key, m)

	return lexKey, withFrontier
}

// zrange key [start stop] => zrange key offset count
func (e *boltZSetEngine) zRangeParse(key []byte, start, stop int64, reverse bool) (int64, int64, error) {
	metaObj, err := e.ZSetMetaObj(key)
	if err != nil {
		return 0, 0, err
	}
	if metaObj == nil {
		return 0, 0, nil
	}

	// convert zero based index
	zz := int64(metaObj.Size)
	if start < 0 {
		if start < -zz {
			start = 0
		} else {
			start = start + zz
		}
	} else {
		if start >= zz {
			return 0, 0, nil
		}
	}

	if stop < 0 {
		if stop < -zz {
			stop = 0
		} else {
			stop = stop + zz
		}
	} else {
		if stop >= zz {
			stop = zz - 1
		}
	}
	if !reverse {
		return start, stop - start + 1, nil
	}

	start, stop = zz-stop-1, zz-start
	return start, stop - start, nil
}
